<?php
	/**
	 * Extension utilities.
	 * @author IMP
	 */

	define('PATH_EXTENSIONS', realpath(PATH_CORE . '/../extensions'));
	
	abstract class Extension {
		private static $extensions = array(),
					   $changed = false,
					   $loaded = array(),
					   $classLinks = array(),
					   $functionLinks = array();
		
		public static final function readExtensions(File $f){
			$ini = parse_ini_file($f->getAbsolutePath(), true);
			 
			$autoload = array();
			self::$extensions = array();
			
			foreach($ini as $name => $section){
				if(is_array($section)){
					if(!empty($section['version'])){
						$classes = !empty($section['classes']) && is_array($section['classes']) ? $section['classes'] : array();
						$functions = !empty($section['functions']) && is_array($section['functions']) ? $section['functions'] : array();
						
						$cfg = new ExtensionConfiguration(
							$name,
							$section['version'],
							!empty($section['description']) ? $section['description'] : '',
							!empty($section['autoload']) ? (bool) $section['autoload'] : false,
							!empty($section['include']) && is_array($section['include']) ? $section['include'] : (!empty($section['include']) ? array($section['include']) : array()),
							!empty($section['extension']) && is_array($section['extension']) ? $section['extension'] : (!empty($section['extension']) ? array($section['extension']) : array()),
							$classes,
							$functions
						);
						
						self::$extensions[$name] = $cfg;
						
						foreach($classes as $c) self::$classLinks[$c] = &self::$extensions[$name];
						foreach($functions as $f) self::$functionLinks[$f] = &self::$extensions[$name];
						
						if($cfg->isAutoloaded()) $autoload[] = &self::$extensions[$name];
					}else{
						throw new ExtensionConfigurationException('Extension section "' . $name . '" does not contain a version value.');
					}
				}
			}
			
			foreach($autoload as $al) self::loadExtension($al);
			
			self::$changed = false;
		}
		
		public static final function loadExtensionByClass($class){
			if(!empty(self::$classLinks[$class]) && !class_exists($class)){
				self::loadExtension(self::$classLinks[$class]);
				return class_exists($class);
			}
		}
		
		public static final function loadExtensionByFunction($function){
			if(!empty(self::$functionLinks[$function]) && !function_exists($function)){
				self::loadExtension(self::$functionLinks[$function]);
				return function_exists($function);
			}
		}
		
		public static final function findExtensionByClass($class){
			if(!empty(self::$classLinks[$class]) && !class_exists($class)){
				return self::$classLinks[$class]->getName();
			}
		}
		
		public static final function findExtensionByFunction($function){
			if(!empty(self::$functionLinks[$function]) && !function_exists($function)){
				return self::$functionLinks[$function]->getName();
			}
		}
		
		public static final function loadExtension($ext){
			if($ext instanceof ExtensionConfiguration){
				ErrorCatcher::beginCatching(E_ALL);
				ob_start();
				
				foreach($ext->getExtensions() as $rext){
					if($rext == $ext->getName()) continue;
					if(!self::loadExtension($rext)) throw new ExtensionConfigurationException('Depending extension "' . $rext . '" does not exist.');
				}
				
				foreach($ext->getIncludes() as $incl){
					cc_include_once(PATH_EXTENSIONS . DS . $ext->getName() . DS . $incl);
				}
				
				ob_clean();
				$got = ErrorCatcher::caughtErrors();
				ErrorCatcher::grabErrors();
				
				if(!$got){
					self::$loaded[$ext->getName()] = &$ext;
				}
				
				return !$got;
			}elseif(is_string($ext) && !empty(self::$extensions[$ext])){
				return self::loadExtension(self::$extensions[$ext]);
			}else{
				return false;
			}
		}
		
		public static final function isLoaded($ext){
			if($ext instanceof ExtensionConfiguration){
				if(isset(self::$loaded[$ext->getName()])){
					return self::$loaded[$ext->getName()] === $ext;
				}else return false;
			}elseif(is_string($ext) && !empty(self::$extensions[$ext])){
				return self::isLoaded(self::$extensions[$ext]);
			}else{
				return false;
			}
		}
		
		public static final function registerExtension(ExtensionConfiguration $cfg){
			if(!empty(self::$extensions[$cfg->getName()])){
				$inclFiles = array_map('realpath', $cfg->getIncludes());
				$backtrace = debug_backtrace(false);
				
				$hit = false;
				
				foreach($backtrace as $trace){
					if(!empty($trace['file']) && in_array($trace['file'], $inclFiles)){
						self::$extensions[$cfg->getName()] = $cfg;
						self::$changed = true;
						$hit = true;
						break;
					}
				}
				
				if(!$hit){
					throw new ExtensionConfigurationException('Extension configuration may only be overwritten within the extension.');
				}
			}else{
				self::$extensions[$cfg->getName()] = $cfg;
				self::$changed = true;
			}
		}
		
		public static final function saveExtensions(File $f){
			$transformer = new IniTransformer();
			foreach(self::$extensions as $ext) $transformer->addSection($ext->generateEntry());
			$transformer->save($f);
		}
		
		public static final function __shutdown(){
			if(self::$changed) self::saveExtensions(new File(PATH_CORE_CONFIGS . DS . 'extensions.ini', true));
		}
	}
	
	register_shutdown_function('Extension::__shutdown');
	spl_autoload_register('Extension::loadExtensionByClass');
?>